<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<script type="text/javascript" src="../lib/dojo.js"></script>
		<script type="text/javascript" src="../calendar.js"></script>
		<script type="text/javascript">
			function date_from_display(p) {
				var m = /^Year (\d+) - (\w+), day (\d+)$/.exec(p.textContent);
				if (!m) {
					throw "bad date string '" + p.textContent + "'";
				}
				return {
					day: +m[3],
					season: m[2],
					year: +m[1]
				};
			}

			function season_name(cal, s) {
				return cal.seasons[s] ? cal.seasons[s].title : "?Season" + s;
			}

			function date_from_obj(cal, obj) {
				return {
					tick: obj.tick,
					day: obj.day,
					season: season_name(cal, obj.season),
					year: obj.year
				};
			}

			function date_from_cal(cal) {
				var d = Math.floor(cal.day + 0.5 / cal.ticksPerDay);
				return date_from_obj(cal, {
					tick: Math.round((cal.day - d) * cal.ticksPerDay),
					day: d,
					season: cal.season,
					year: cal.year
				});
			}

			function date_to_str(obj) {
				return "Year " + obj.year + " " + obj.season + " day " + obj.day;
			}

			function date_equal(d1, d2) {
				return d1.day === d2.day && d1.season === d2.season && d1.year === d2.year;
			}

			function main () {
				var display, log, cal, events, pass, mydate, msgtypes;

				display = document.getElementById("caldisplay");

				log = document.getElementById("log");

				cal = new com.nuclearunicorn.game.Calendar(
					/* provide fake game object to minimize dependencies */
					{
						science: {
							get: function () {
								return {researched: true};
							}
						},
						ironWill: false
					},
					display);

				events = [];

				function event_handler (event) {
					return function () {
						events.push({event: event, date: date_from_cal(cal)});
					};
				}

				cal.onNewDay = event_handler("newDay");
				cal.onNewSeason = event_handler("newSeason");
				cal.onNewYear = event_handler("newYear");

				pass = true;

				mydate = {
					tick: 0,
					day: 0,
					season: 0,
					year: 0
				};

				function write_message(status, msg) {
					var p = document.createElement("P");
					p.textContent = status + " " + msg;
					log.appendChild(p);
				}

				function check_date_sync() {
					var d1 = date_from_display(display),
					    d2 = date_from_cal(cal),
						d3 = date_from_obj(cal, mydate);

					if (!date_equal(d1, d2)) {
						pass = false;
						write_message("FAIL", "Displayed date '" + date_to_str(d1) + "' does not match internal date '" + date_to_str(d2) + "'");
					}

					if (!date_equal(d2, d3)) {
						pass = false;
						write_message("FAIL", "Internal date '" + date_to_str(d2) + "' does not match computed date '" + date_to_str(d3) + "'");
					}
				}

				function event_error(event, msg) {
					var d = date_from_obj(cal, mydate);
					pass = false;
					write_message("FAIL", "During " + event + " on " + date_to_str(d) + ": " + msg);
				}

				function tickfn() {

					/* update calendar */
					cal.tick();

					/* The mydate values have not been changed yet, so they still contain the prior date.
					   If there were any events, mydate will be the date just before the rollover
					   and can be used to check if the length of each day/season/year is correct.
					   In particular, since the date system is 0-based, the number of ticks/days/seasons/
					   is the value of the final tick/day/season plus 1.
					 */
					events.forEach(function (e) {
						var v;
						switch (e.event) {
						case "newDay":
							v = mydate.tick + 1;
							if (v !== Math.round(cal.ticksPerDay)) {
								 event_error(e.event, "wrong number of ticks in the day: " + v);
							}
							break;
						case "newSeason":
							v = mydate.day + 1;
							if (v !== cal.daysPerSeason) {
								event_error(e.event, "wrong number of days in the season: " + v);
							}
							break;
						case "newYear":
							v = mydate.season + 1;
							if (v !== cal.seasonsPerYear) {
								event_error(e.event, "wrong number of seasons in the year: " + v);
							}
							break;
						}
					});

					/* update computed date */
					mydate.tick += 1;
					events.forEach(function (e) {
						switch (e.event) {
						case "newDay":
							mydate.tick = 0;
							mydate.day += 1;
							break;
						case "newSeason":
							mydate.day = 0;
							mydate.season += 1;
							break;
						case "newYear":
							mydate.season = 0;
							mydate.year += 1;
							break;
						}
					});

					/* checkpoint: all dates should now be in sync */
					check_date_sync();

					/* the events are supposed to fire at the beginning of the day/season/year,
					   so the calendar internal date captured during the event should match the computed date
					*/
					events.forEach(function (e) {
						var d1 = e.date,
						    d2 = date_from_obj(cal, mydate);
						if (!date_equal(d1, d2)) {
							pass = false;
							write_message("FAIL", "Date during event " + date_to_str(d1) + " does not match computed date " + date_to_str(d2));
						}
					});

					events = [];

					if (mydate.year > 1) {
						if (pass) {
							write_message("PASS", "all tests passed");
						}
						write_message("DONE", "Tests complete");
						return;
					}

					/* This is a timeout loop instead of a regular for-loop
					   to force the browser to render the page while the test is running
					   instead of waiting until the end
					*/
					setTimeout(tickfn, 1);
				}

				/* execution starts here */

				/* initial render */
				cal.update();

				/* the initial dates should be in sync*/
				check_date_sync();

				tickfn();
			}
		</script>
	</head>
	<body onload="main();">
		<div id="caldisplay" style="margin:auto;width:600px;"></div>
		<div id="log" style="margin:auto;width:900px;height:600px;overflow:auto;white-space:pre;"></div>
	</body>
</html>
